package procilege.impl.expand;

import java.util.HashMap;
import java.util.Map;

import org.apache.solr.util.OpenBitSet;

import procilege.compute.ExprExpand;
import procilege.compute.ExprExpandTraveller;
import procilege.compute.PlaceTypeMetadata;
import procilege.compute.PlaceType;
import procilege.compute.TravelMap;
import procilege.impl.NameImpl;
import procilege.lang.CompositeExpr;
import procilege.lang.ExprElement;
import procilege.lang.Name;

import com.google.inject.Inject;
import com.google.inject.Injector;

public class ExprExpandImpl implements ExprExpand,
		TravelMap<ExprExpandTraveller> {

	@Inject
	private Injector injector;

	private Map<Class<?>, PlaceType<ExprExpandTraveller>> map;

	@SuppressWarnings("unchecked")
	public CompositeExpr<? extends ExprElement> expand(ExprElement expr) {

		ExprExpandTraveller traveller = traveller(null);
		traveller.setout(expr);

		Object r = traveller.getPreviousVisitResult();
		if (r instanceof CompositeExpr)
			return (CompositeExpr<? extends ExprElement>) r;
		else if (r != null) {
			throw new IllegalStateException("unknow obj: " + r);
		} else
			return null;
	}

	public ExprExpandTraveller traveller(Name boundname) {

		OpenBitSet bn = new OpenBitSet();
		if (boundname != null)
			bn.set(((NameImpl) boundname).nameId);

		ExprExpandTravellerImpl tr = new ExprExpandTravellerImpl(this, bn);
		injector.injectMembers(tr);

		return tr;
	}

	public synchronized void configure() {

		map = new HashMap<Class<?>, PlaceType<ExprExpandTraveller>>();

		register(BoolExprAndExpandPlace.class);
		register(BoolExprOrExpandPlace.class);
		register(EntityExprAndExpandPlace.class);
		register(EntityExprOrExpandPlace.class);
		register(NameExpandPlace.class);
		register(PredicateEntityExpandPlace.class);
		register(PredicateEntityPropertyExpandPlace.class);
		register(PredicateFunctionExpandPlace.class);
		register(PredicateObjectPropertyExpandPlace.class);
		register(PredicatePrivAvailiabilityExpandPlace.class);
		register(PredicateValueExpandPlace.class);
		register(PrivExprCompositionExpandPlace.class);
		register(PrivExprMergenceExpandPlace.class);
		register(RelationalExprExpandPlace.class);
		register(RelationConformExpandPlace.class);
		register(ExprSubstituteExpandPlace.class);
	}

	private void register(Class<? extends PlaceType<ExprExpandTraveller>> oprcls) {

		PlaceTypeMetadata ot = oprcls.getAnnotation(PlaceTypeMetadata.class);
		Class<?> target = ot.type();
		try {
			PlaceType<ExprExpandTraveller> op = oprcls.newInstance();
			injector.injectMembers(op);
			map.put(target, op);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public PlaceType<ExprExpandTraveller> find(Class<?> cls) {

		if (map == null) {
			configure();
		}

		return map.get(cls);
	}
}